'''
BP神经网络实验
'''
import numpy as np


def tanh(x):
    return np.tanh(x)
 
def tanh_deriv(x):
    return 1.0-np.tanh(x)*np.tanh(x)

# 激活函数为 sigmoid函数
def logistic(x):
    return 1 / (1 + np.exp(-x))


# sigmoid函数的导数
def logistic_derivative(x):
    return logistic(x) * (1 - logistic(x))


class NeuralNetwork:
    def __init__(self, layers):
        """
        神经网络算法构造函数
        :param layers: 一个列表，包含了每层神经网络中有几个神经元，至少有两层，输入层不算作
                [, , ,]中每个值代表了每层的神经元个数
        :param activation: 使用的函数（默认tanh函数）
        :return:none
        """

        self.activation = logistic
        self.activation_deriv = logistic_derivative
        #self.activation = tanh
        #self.activation_deriv = tanh_deriv


        # 权重列表
        self.weights = []
        # 初始化权重（随机）
            #np.random.random为nunpy随机产生的数
            #实际是以第二层开始，前后都连线赋予权重，权重位于[-0.25,0.25]之间
        for i in range(1, len(layers) - 1):
            self.weights.append((2 * np.random.random((layers[i - 1] + 1, layers[i] + 1)) - 1) * 0.25)
            self.weights.append((2 * np.random.random((layers[i] + 1, layers[i + 1])) - 1) * 0.25)

    def fit(self, X, y, learning_rate=0.2, epochs=20000):
        """
        训练神经网络
        :param X: 数据集（通常是二维）
        :param y: 分类标记
        :param learning_rate: 学习率（默认0.2）
        :param epochs: 训练次数（最大循环次数，默认10000）
        :return: none
        """
        # 确保数据集是二维的
        X = np.atleast_2d(X)  #atleast_xd 支持将输入数据直接视为 x维
        
        #np.ones初始化一个矩阵，传入两个参数全是1
        #X.shape返回的是一个list[行数，列数]
        #X.shape[0]返回的是行，X.shape[1]+1：比X多1，对bias进行赋值为1

        temp = np.ones([X.shape[0], X.shape[1] + 1])  #ones(x,y) 生成一个x行y列的二维数组，value全为1
        
        #“ ：”取所有的行
        #“0：-1”从第一列到倒数第二列，-1代表的是最后一列
        temp[:, 0:-1] = X 
        X = temp
        #y：classlabel，函数的分类标记
        y = np.array(y)
        #k代表的是第几轮循环 
        for k in range(epochs):
            # 从0到X.shape[0]随机抽取一行做实例
            i = np.random.randint(X.shape[0])
            # 用随机抽取的这一组数据对神经网络更新
            a = [X[i]]
            # 正向更新  , len(self.weights)等于神经网络层数
            for l in range(len(self.weights)):
                #np.dot代表两参数的内积，x.dot(y) 等价于 np.dot(x,y)
                #即a与weights内积，之后放入非线性转化function求下一层
                #a输入层，append不断增长，完成所有正向的更新
                a.append(self.activation(np.dot(a[l], self.weights[l])))  #np.dot()做矩阵乘法,
            # 计算错误率，y[i]真实标记   ，a[-1]预测的classlable
            error = y[i] - a[-1]   
            #计算输出层的误差，根据最后一层当前神经元的值，反向更新
            deltas = [error * self.activation_deriv(a[-1])]  # 求神经元权重的导数

            # 反向更新
            ###########开始1#############
            
            #len(a)所有神经元的层数，不能算第一场和最后一层
            #从最后一层到第0层，每次-1
             # 求梯度
            for l in range(len(a) - 2, 0, -1):
                deltas.append(deltas[-1].dot(self.weights[l].T) * self.activation_deriv(a[l]))

            # 反向排序, 将deltas的层数跌倒过来
            deltas.reverse()

            # 梯度下降法更新权值
            for i in range(len(self.weights)):
                layer = np.atleast_2d(a[i])
                delta = np.atleast_2d(deltas[i]) #delta代表的是权重更新
                self.weights[i] += learning_rate * layer.T.dot(delta) # layer.T.dot(delta)误差和单元格的内积

            ###########结束1#############

    def predict(self, x):
        x = np.array(x)
        temp = np.ones(x.shape[0] + 1)
        #从0行到倒数第一行
        temp[0:-1] = x
        a = temp
        for l in range(0, len(self.weights)):
            a = self.activation(np.dot(a, self.weights[l]))
        return a


if __name__ == '__main__':
    # X:                  Y
    # 0 0                 0
    # 0 1                 1
    # 1 0                 1
    # 1 1                 0
    nn = NeuralNetwork([2, 2, 1])
    temp = [[0, 0], [0, 1], [1, 0], [1, 1]]
    X = np.array(temp)
    y = np.array([0, 1, 1, 0])
    nn.fit(X, y)

    flag = 0
    for i in temp:
        print(i, nn.predict(i))  # 可以测试输出预测结果
        if (nn.predict(i) >= 0.40) and (nn.predict(i) <= 0.60):
            flag = flag + 1
    if flag == 4:
        print('success')